module ext.traits;

import std.traits;
import std.typetuple;

import ext.lang;
import ext.demangle;
import ext.contracts;
import ext.typetuple;
import ext.functional;


/*
The content of $(D a) is the same as of $(D b) except order.
 */
version (unittest)
private bool consistsOf(T)(T[] a, T[] b)
{
    if (a.length != b.length)
        return false;
    static bool impl(T[] a, T[] b, size_t n)
    {
        if (a.length == 0)
            return true;
        if (n == a.length)
            return false;
        if (a[0] == b[0])
            return consistsOf(a[1 .. $], b[1 .. $]);
        return impl(a[1 .. $] ~ a[0], b, n + 1);
    }
    return impl(a, b, 0);
}


/*
The getVirtualFunctions trait returns a weird tuple. This template just
reconstructs the tuple.
 */
private template getVirtualFunctions(C, string m)
{
    alias getVirtualFunctionsImpl!(C, m).all getVirtualFunctions;
}

private template getVirtualFunctionsImpl(C, string m)
{
    enum size_t max = __traits(getVirtualFunctions, C, m).length;

    template next(size_t i)
    {
        static if (i < max)
            alias TypeTuple!(
                    __traits(getVirtualFunctions, C, m)[i],
                    next!(i + 1)) next;
        else
            alias TypeTuple!() next;
    }
    alias next!(0) all;
}


/*
Returns $(D true) if $(D f) and $(D g) have the same type.
 */
private template sameType(alias f, alias g,
        bool __r = is(typeof(f) == typeof(g)))
{
    enum bool sameType = __r;
}


/*
Returns the name of the function $(D F).
 */
private template functionName(alias F)
    if (is(typeof(F) == function))
{
    enum string functionName = TypeTuple!(F).stringof[6 .. $ - 1];
}

unittest
{
    static void foo() {}
    static assert(functionName!(foo) == "foo");
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/***************************************
Returns a type tuple consisting of the types of $(D S) struct members
whose _names are given in the $(D names) parameter. The _names must be
existing member _names.

Example:
--------------------
struct S
{
    int num;
    string name;
    real value;
}
alias MemberTypeTuple!(S, "name", "value") MTT;
static assert(is(MTT == TypeTuple!(string, real)));
--------------------
 */
template MemberTypeTuple(S, names...)
{
    static if (names.length > 0)
        alias TypeTuple!(
                typeof(__traits(getMember, S, names[0])),
                MemberTypeTuple!(S, names[1 .. $])
            ) MemberTypeTuple;
    else
        alias TypeTuple!() MemberTypeTuple;
}

unittest
{
    struct S
    {
        int num;
        string name;
        real value;
    }
    alias MemberTypeTuple!(S, "name", "value") MTT;
    static assert(is(MTT == TypeTuple!(string, real)));
}


/***************************************
Constructs a struct $(D S) object with the initial values $(D init) for
the _fields specified in $(D fields). The other (unspecified) fields
will be initialized with their default values.

This function can be executed at compile time.

Params:
  fields = The names of the _fields to be initialized.
  init   = The initial values for the _fields.

Example:
--------------------
struct S
{
    string s;
    real r = 2.81828;
    int n;
}
enum S s = makeStruct!(S, "s", "n")("compile time", 42);
static assert(s.s == "compile time");
static assert(s.r == 2.81828);
static assert(s.n == 42);
--------------------
 */
S makeStruct(S, fields...)(MemberTypeTuple!(S, fields) init) nothrow
{
    S s;
    foreach (i, field; fields)
        __traits(getMember, s, fields[i]) = init[i];
    return s;
}

unittest
{
    struct S
    {
        int i;
        string s;
        void* p;
        real r;
    }
    auto s1 = makeStruct!(S, "i", "s", "r")(42, "foo", 2.71828);
    assert(s1 == S(42, "foo", null, 2.71828));
    auto s2 = makeStruct!(S, "r", "s", "i")(.682689, "bar", 105);
    assert(s2 == S(105, "bar", null, .682689));
    auto s3 = makeStruct!(S)();
    assert(s3 == S.init);
    enum s4 = makeStruct!(S, "s")("baz");
    static assert(s4.s == "baz");
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/***************************************
Strips all typedefs from the given type $(D T).

Example:
--------------------
enum E : int { a }
typedef E F;
typedef F G;
static assert(is(OriginalType!G == int));
--------------------
 */
template OriginalType(T)
{
    static if (is(T U == typedef))
        alias OriginalType!U OriginalType;
    else static if (is(T U == enum))
        alias OriginalType!U OriginalType;
    else
        alias T OriginalType;

    debug static assert(is(T : OriginalType));
}

unittest
{
    typedef int T;
    typedef T   U;
    enum V : U { a }
    static assert(is(OriginalType!T == int));
    static assert(is(OriginalType!U == int));
    static assert(is(OriginalType!V == int));
    enum E : int { a }
    enum F : E   { a = E.a }
    typedef F G;
    static assert(is(OriginalType!E == int));
    static assert(is(OriginalType!F == int));
    static assert(is(OriginalType!G == int));
}


/***************************************
Returns the most derived type of $(D T) and $(D U).
 */
template MostDerivedType(T, U...)
{
    static if (U.length > 0)
    {
        static if (is(T : U[0]))
            alias MostDerivedType!(T, U[1 .. $]) MostDerivedType;
        else static if (is(U[0] : T))
            alias MostDerivedType!(U) MostDerivedType;
        else
            static assert(0, "MostDerivedType: " ~ T.stringof ~
                    " and " ~ U[0].stringof ~ " are distinct types");
    }
    else
    {
        alias T MostDerivedType;
    }
}

unittest
{
    alias MostDerivedType!(int*, const(int)*, const(void)*) T1;
    static assert(is(T1 == int*));

    alias MostDerivedType!(Object, Exception) T2;
    static assert(is(T2 == Exception));

    typedef int T;
    enum U : T { a }
    alias MostDerivedType!(int, T, U) T3;
    static assert(is(T3 == U));
}


/***************************************
Detects whether $(D T) is a class or interface type.
 */
template isClass(T)
{
    enum bool isClass = is(T == class) || is(T == interface);
}


/***************************************
Detects whether $(D T) is a pointer type.
 */
template isPointer(T)
{
    static if (is(T E : E*))
        enum bool isPointer = true;
    else
        enum bool isPointer = false;
}


/***************************************
Detects whether $(D T) is a class, interface, array or pointer type.
 */
template isReferenceType(T)
{
    enum bool isReferenceType =
        isClass!(T) || isArray!(T) || isPointer!(T);
}


/***************************************
Detect whether $(D func) is an abstract function.
 */
template isAbstractFunction(alias func,
        // [workaround] not work inside template
        bool _r = __traits(isAbstractFunction, func))
{
    enum bool isAbstractFunction = _r;
}

unittest
{
    class C
    {
        void foo() {}
        abstract void bar();
        final void baz() {}
    }
    static assert(!isAbstractFunction!(C.foo));
    static assert( isAbstractFunction!(C.bar));
    static assert(!isAbstractFunction!(C.baz));
}


/***************************************
Detect whether $(D func) is a final function.
 */
template isFinalFunction(alias func,
        // [workaround] not work inside template
        bool _r = __traits(isFinalFunction, func))
{
    enum bool isFinalFunction = _r;
}

unittest
{
    class C
    {
        void foo() {}
        abstract void bar();
        final void baz() {}
    }
    static assert(!isFinalFunction!(C.foo));
    static assert(!isFinalFunction!(C.bar));
    static assert( isFinalFunction!(C.baz));
}


/***************************************
Detects whether the types $(D T) and $(D U) are covariant return types.
Return types are covariant if one type is a subtype of the other types
and implicitly convertible to the types.

Use $(D MostDerivedType) for obtaining the most derived type of $(D T)
and $(D U), which should be the appropriate return type of a function
overriding functions whose return types are $(D T) and $(D U).

Example:
--------------------
// Exception is a subtype of Throwable and Object
static assert(isCovariantReturnType!(Throwable, Object, Exception));

// T is a subtype of int
typedef int T;
static assert(isCovariantReturnType!(int, T));

// short is implicitly convertible to int, but not covariant with int
static assert(!isCovariantReturnType!(short, int));

// shared(int)* is not implicitly convertible to int*
static assert(!isCovariantReturnType!(shared(int)*, int*));
--------------------
 */
template isCovariantReturnType(T, U...)
{
    static if (U.length > 0)
    {
        static if (isCovariantReturnTypePrim!(T, U[0]).yes)
            enum bool isCovariantReturnType =
                isCovariantReturnType!(
                    MostDerivedType!(T, U[0]), U[1 .. $]);
        else
            enum bool isCovariantReturnType = false;
    }
    else
    {
        enum bool isCovariantReturnType = true;
    }
}

private template isCovariantReturnTypePrim(R, S)
{
    alias OriginalType!R RR;
    alias OriginalType!S SS;

    /* We will see if one type is implicitly convertible to the other
     * type. That is necessary but not sufficient to determine whether
     * the types are covariant. For example, short is implicitly
     * convertible to int, but they are not covariant.
     */
    static if (isReferenceType!(RR) && isReferenceType!(SS))
    {
        // For reference types, we can safely assume that a type is a
        // subtype of another type if the former is implicitly
        // convertible to the latter.
        enum premise = true;
    }
    else
    {
        // For value types, we must see a supplimental condition:
        //    - The types are the same except mutability.
        // The following does the check:
        enum premise = is(RR* : SS*) || is(SS* : RR*);

        debug static assert(!is(short* : int*));
        debug static assert( is(short* : const(short)*));
    }

    enum yes = premise && (is(R : S) || is(S : R));
}

unittest
{
    // on polymorphism
    struct Scope1
    {
        class C {}
        interface I {}
        interface J : I {}
        class D : C {}
        class E : I {}
        static assert(!isCovariantReturnType!(C, I));
        static assert(!isCovariantReturnType!(C, J));
        static assert( isCovariantReturnType!(C, D));
        static assert(!isCovariantReturnType!(C, E));
        static assert( isCovariantReturnType!(I, J));
        static assert(!isCovariantReturnType!(I, D));
        static assert( isCovariantReturnType!(I, E));
        static assert(!isCovariantReturnType!(J, D));
        static assert(!isCovariantReturnType!(J, E));
    }
    // on mutability
    struct Scope2
    {
        alias              int  * M;
        alias        const(int) * C;
        alias    immutable(int) * I;
        alias       shared(int) * S;
        alias const(shared(int))* K;
        static assert( isCovariantReturnType!(M, C));
        static assert(!isCovariantReturnType!(M, I));
        static assert(!isCovariantReturnType!(M, S));
        static assert(!isCovariantReturnType!(M, K));
        static assert( isCovariantReturnType!(C, I));
        static assert( isCovariantReturnType!(C, S));
        static assert( isCovariantReturnType!(C, K));
        static assert(!isCovariantReturnType!(I, S));
        static assert(!isCovariantReturnType!(I, K));
    //  static assert( isCovariantReturnType!(S, K)); // BUG
    }
    // on typedef & enum
    struct Scope3
    {
        alias int I;
        typedef I J;
        typedef J K;
        static assert( isCovariantReturnType!(I, J));
        static assert( isCovariantReturnType!(I, K));
        static assert( isCovariantReturnType!(J, K));
        enum E : I { a }
        enum F : E { a = E.a }
        static assert( isCovariantReturnType!(I, E));
        static assert( isCovariantReturnType!(I, F));
        static assert( isCovariantReturnType!(E, F));
    }
    // misc.
    struct Scope4
    {
        class C {}
        interface I {}
        static assert(!isCovariantReturnType!(C, void*));
        static assert(!isCovariantReturnType!(I, void*));
        static assert(!isCovariantReturnType!(int, short));
        static assert(!isCovariantReturnType!(int, float));
        static assert(!isCovariantReturnType!(int[], uint[]));
        static assert( isCovariantReturnType!(int*, void*));
        static assert( isCovariantReturnType!(void, void));
    }
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
// Function traits
// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/***************************************
Represents a paramter type with a parameter storage class.
 */
template Parameter(T, StorageClass s)
{
    static assert((s & ~StorageClass.PARAMETER) == 0);

    /**
     * The type of the parameter.
     */
    alias T Type;

    /**
     * The parameter _storage class of the parameter.
     */
    enum StorageClass storage = s;
}

/*
Converts a tuple of $(D Parameter)s into a string which represents the
parameter declarations.
 */
package template Parameters_toString(Params...)
{
    static if (Params.length > 1)
        enum string Parameters_toString =
            Parameters_toString!(Params[0]) ~ ", " ~
            Parameters_toString!(Params[1 .. $]);

    static if (Params.length == 1)
        enum string Parameters_toString =
            appendStorageClass(
                Params[0].Type.stringof, Params[0].storage);

    static if (Params.length == 0)
        enum string Parameters_toString = "";
}

unittest
{
    alias Parameter!(int, StorageClass.OUT) P1;
    alias Parameter!(void*, StorageClass.NONE) P2;

    static assert(Parameters_toString!() == "");
    static assert(Parameters_toString!(P1) == "out int");
    static assert(Parameters_toString!(P1, P2) == "out int, void*");
}


/***************************************
Collection of information on a function.
 */
template FunctionTraits(
        string name_,
        StorageClass storage_, Linkage convention_,
        FunctionAttribute attributes_, Variadic variadic_,
        ReturnType_, Parameters_...)
{
    /**
     * The _name of the function (can be empty).
     */
    enum string name = name_;

    /**
     * The type-constructing _storage class, if any, of the function
     * itself. A type-constructing _storage class is $(D shared),
     * $(D const), $(D immutable) or $(D shared const).
     */
    enum StorageClass storage = storage_;

    /**
     * The calling _convention.
     */
    enum Linkage convention = convention_;

    /**
     * The function _attributes.
     */
    enum FunctionAttribute attributes = attributes_;

    /**
     * The function variadicity (whether _variadic or not, and if so,
     * what kind of _variadic function).
     */
    enum Variadic variadic = variadic_;

    /**
     * The return type.
     */
    alias ReturnType_ ReturnType;

    /**
     * A tuple of $(D Parameter) template instances representing the
     * parameters of the function.
     */
    alias Parameters_ Parameters;


    /**
     * Returns a new $(D FunctionTraits) instance with a new _name.
     */
    template rename(string name)
    {
        alias FunctionTraits!(
                name,
                storage_, convention_, attributes_, variadic_,
                ReturnType_, Parameters_
            ) rename;
    }
}

unittest
{
    alias FunctionTraits!("sin",
            StorageClass.CONST, Linkage.C, FunctionAttribute.NOTHROW,
            Variadic.TYPESAFE,
            double, Parameter!(double, StorageClass.NONE)
        ) F;
    alias F.rename!("cos") G;
    static assert(G.name == "cos");
    static assert(G.storage == F.storage);
    static assert(G.convention == F.convention);
    static assert(G.attributes == F.attributes);
    static assert(G.variadic == F.variadic);
    static assert(is(G.ReturnType == F.ReturnType));
    static assert(__traits(isSame, G.Parameters, F.Parameters));
}

/**
These templates constructs a $(D FunctionTraits) instance from an
existing function or function type $(D F).
 */
template FunctionTraits(alias F, _F = typeof(F))
    if (is(_F == function))
{
    alias .FunctionTraits!(_F).rename!(functionName!(F)) FunctionTraits;
}

/// Ditto
template FunctionTraits(F)
    if (is(F == function))
{
    alias MakeFunctionTraits!(F).Traits FunctionTraits;
}

// [internal]
private template MakeFunctionTraits(F)
{
    private
    {
        enum typeinfo = decodeType(mangleof!(F));
        enum typeinfo_next       = typeinfo.next;
        enum typeinfo_storage    = typeinfo.storage;
        enum typeinfo_linkage    = typeinfo.linkage;
        enum typeinfo_attributes = typeinfo.attributes;
        enum typeinfo_variadic   = typeinfo.variadic;

        enum paraminfo = typeinfo_next[1 .. $];
        alias ParameterTypeTuple!(F) ParameterTypes;
        static assert(paraminfo.length == ParameterTypes.length);
    }

    // Constructs Parameter instances on i, i+1, ...-th parameters.
    private template GenerateParams(size_t i)
    {
        static if (i < ParameterTypes.length)
            alias TypeTuple!(
                    Parameter!(
                        ParameterTypes[i],
                        paraminfo[i].storage & StorageClass.PARAMETER),
                    GenerateParams!(i + 1))
                GenerateParams;
        else
            alias TypeTuple!() GenerateParams;
    }

    alias FunctionTraits!("",
            typeinfo_storage, typeinfo_linkage,
            typeinfo_attributes, typeinfo_variadic,
            ReturnType!(F), GenerateParams!(0)
        ) Traits;
}

unittest
{
    struct S
    {
        extern(C) void foo(real, lazy short[]...) const nothrow {}
    }
    alias FunctionTraits!(S.foo) FT;
    static assert(FT.storage    == StorageClass.CONST);
    static assert(FT.convention == Linkage.C);
    static assert(FT.attributes == FunctionAttribute.NOTHROW);
    static assert(FT.variadic   == Variadic.TYPESAFE);
    static assert(is(FT.ReturnType == void));
    static assert(FT.Parameters.length == 2);
    static assert(is(FT.Parameters[0].Type == real));
    static assert(FT.Parameters[0].storage == StorageClass.NONE);
    static assert(is(FT.Parameters[1].Type == short[]));
    static assert(FT.Parameters[1].storage == StorageClass.LAZY);
}

/*
Returns a human-readable string representation of the given
$(D FunctionTraits)-compatible instance $(D F).
 */
private template FunctionTraits_toString(alias F)
{
    enum string FunctionTraits_toString =
        FunctionTraits_toStringImpl!(F).result;
}

// [internal]
private template FunctionTraits_toStringImpl(alias F)
{
    enum string name = (F.name.length ? F.name : "function");
    enum string params =
        appendVararg(Parameters_toString!(F.Parameters), F.variadic);
    enum string core =
        F.ReturnType.stringof ~ " " ~ name ~ "(" ~ params ~ ")";

    enum string result =
        core.prefixFunction(F.convention, F.storage, F.attributes);
}


/* *************************************
Determine whether the functions $(D F) and $(D G) can be overridden by
a common function.

Params:
  needmsg = When set to $(D true), a detailed error message will be
    provided if the functions are not covariant. Generating the error
    message would slow down compilation, so you should set this to
    $(D true) if the error message is unnecessary.

Returns:
  covariant = $(D True) if $(D F) and $(D G) are covariant.
  msg       = An error message describing why the functions are not
    covariant. It is $(D null) if $(D needmsg == true) or the functions
    ARE covariant.
 */
package template checkCovariance(alias F, alias G, bool needmsg = true)
{
private:
    alias QuietAssert!(F.storage == G.storage,
            "Function storage classes don't match.") Astorage;

    alias QuietAssert!(F.convention == G.convention,
            "Calling conventions don't match.") Aconvention;

    alias QuietAssert!(
            !((F.attributes ^ G.attributes) & FunctionAttribute.REF),
            "One function is ref while the other is not.") Aattributes;

    alias QuietAssert!(F.variadic == G.variadic,
            "Variadic arguments don't match.") Avariadic;

    alias QuietAssert!(
            isCovariantReturnType!(F.ReturnType, G.ReturnType),
            "Return types are not covariant.") AreturnType;

    alias QuietAssert!(
            PackedTuple!(F.Parameters).equals!(G.Parameters),
            "Parameters don't match.") Aparameters;

public:
    enum bool covariant =
        Astorage   .succeeds && Aconvention.succeeds &&
        Aattributes.succeeds && Avariadic  .succeeds &&
        AreturnType.succeeds && Aparameters.succeeds ;

    static if (!needmsg || covariant)
    {
        enum string msg = "Functions are not covariant";
    }
    else
    {
        private template item(alias a)
        {
            enum string item = a.succeeds ? "" : ("\n  - " ~ a.msg);
        }
        enum string msg =
            "Functions are not covariant." ~
            "\n" ~ FunctionTraits_toString!(F) ~
            "\n" ~ FunctionTraits_toString!(G) ~
            item!(Astorage   ) ~ item!(Aconvention) ~
            item!(Aattributes) ~ item!(Avariadic  ) ~
            item!(AreturnType) ~ item!(Aparameters) ;
    }
}


/***************************************
Returns $(D true) if the functions represented by the given
$(D FunctionTraits) instances $(D F) and $(D G) are return-type
covariant overloads.

Functions are return-type covariant overloads if the following criteria
are met:
$(UL
  $(LI The storage classes of the functions are the same.)
  $(LI The calling conventions of the functions are the same.)
  $(LI None or all of the functions are ref functions.)
  $(LI The return types of the functions are covariant.)
  $(LI Parameters are the same including variadic ones (if any).)
)
 */
template isCovariantFunctionTraits(alias F, alias G)
{
    enum bool isCovariantFunctionTraits =
        checkCovariance!(F, G, false).covariant;
}

unittest
{
    // storage class
    struct Scope1
    {
        interface I
        {
            void F1();
            void F2() const;
        }
        alias FunctionTraits!(I.F1) F1;
        alias FunctionTraits!(I.F2) F2;
        static assert( isCovariantFunctionTraits!(F1, F1));
        static assert(!isCovariantFunctionTraits!(F1, F2));
    }
    // calling convention
    struct Scope2
    {
        interface I
        {
            extern(D) void F1();
            extern(C) void F2();
        }
        alias FunctionTraits!(I.F1) F1;
        alias FunctionTraits!(I.F2) F2;
        static assert( isCovariantFunctionTraits!(F1, F1));
        static assert(!isCovariantFunctionTraits!(F1, F2));
    }
    // ref-ness
    struct Scope3
    {
        interface I
        {
                int F1();
            ref int F2();
        }
        alias FunctionTraits!(I.F1) F1;
        alias FunctionTraits!(I.F2) F2;
        static assert( isCovariantFunctionTraits!(F1, F1));
        static assert(!isCovariantFunctionTraits!(F1, F2));
    }
    // covariant return type
    struct Scope4
    {
        interface I
        {
            interface X     {}
            interface Y : X {}
            interface Z     {}
            X F1();
            Y F2();
            Z F3();
        }
        alias FunctionTraits!(I.F1) F1;
        alias FunctionTraits!(I.F2) F2;
        alias FunctionTraits!(I.F3) F3;
        static assert( isCovariantFunctionTraits!(F1, F1));
        static assert( isCovariantFunctionTraits!(F1, F2));
        static assert(!isCovariantFunctionTraits!(F2, F3));
        static assert(!isCovariantFunctionTraits!(F3, F1));
    }
    // parameters
    struct Scope5
    {
        interface I
        {
            void F1(int, ...);
            void F2(int);
            void F3(ref int);
        }
        alias FunctionTraits!(I.F1) F1;
        alias FunctionTraits!(I.F2) F2;
        alias FunctionTraits!(I.F3) F3;
        static assert( isCovariantFunctionTraits!(F1, F1));
        static assert(!isCovariantFunctionTraits!(F1, F2));
        static assert(!isCovariantFunctionTraits!(F2, F3));
        static assert(!isCovariantFunctionTraits!(F3, F1));
    }
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
// Method names
// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/***************************************
Returns a literal array of strings, each of which is a non-static
member function name defined in $(D C) and its ancestors. The result
does not contain duplicate names.

Example:
--------------------
import std.stdio;
interface I { void foo(); }
interface J { void bar(); }
abstract class C : I, J
{
    void bar(int);
    static void baz();
}
void main()
{
    writeln(methodNames!(C));
}
--------------------
which prints
--------------------
bar toString toHash opCmp opEquals foo
--------------------
 */
template methodNames(C)
{
    // The raw result of allMembers contains member variables.
    enum methodNames = selectMethodNames!(C, __traits(allMembers, C));
}

// [internal]
private template selectMethodNames(C, alias names)
{
    static if (names.length)
    {
        // eliminate static functions
        static if (MethodsTuple!(C, names[0]).length)
            enum selectMethodNames = names[0] ~
                selectMethodNames!(C, names[1 .. $]);
        else
            enum selectMethodNames =
                selectMethodNames!(C, names[1 .. $]);
    }
    else
    {
        enum selectMethodNames = names; // empty
    }
}

unittest
{
    struct Scope1
    {
        interface I
        {
            void foo();
            void foo(int);
            void bar();
        }
        static assert(consistsOf(methodNames!(I), [ "foo", "bar" ]));

        class B        { abstract void foo(); int x; }
        class C : B, I { abstract void bar(); static void baz() {} }
        enum o = methodNames!(Object);
        enum b = methodNames!(B);
        enum c = methodNames!(C);
        static assert(consistsOf(b, o ~ [ "foo" ]));
        static assert(consistsOf(c, o ~ [ "foo", "bar" ]));
    }
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
// Enumerating methods
// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/***************************************
Returns a static tuple containing all member function overloads of the
name $(D m) defined in $(D C) and its ancestors without duplicates.

Example:
--------------------
interface I { I foo(); }
interface J { J foo(); I foo(); }
class C : I, J
{
    C foo() { return null; }
}
pragma(msg, typeof(MethodsTuple!(C, "foo")).stringof);
--------------------
which prints
--------------------
(C(), I(), J())
--------------------
 */
template MethodsTuple(C, string m)
{
    alias MethodsTupleImpl!(C, m).all MethodsTuple;
}

// [internal]
private template MethodsTupleImpl(C, string m)
{
    /*
     * This template gathers overloads in ancestor classes and
     * interfaces. There would be duplicates in the result.
     *
     * The getVirtualFunctions trait actually returns an overload set,
     * not all overloads; i.e. hidden functions are excluded. That's
     * why this template is needed.
     */
    template ancestorsOverloads()
    {
        template dig(C...)
        {
            static if (C.length)
                alias TypeTuple!(
                        MethodsTuple!(C[0], m),
                        dig!(C[1 .. $])) dig;
            else
                alias TypeTuple!() dig;
        }
        static if (is(C S == super))
            alias dig!(S) all;
        else
            alias TypeTuple!() all;
    }

    /*
     * Doit.
     */
    static if (__traits(hasMember, C, m))
        alias TypeTuple!(
            getVirtualFunctions!(C, m),
            ancestorsOverloads!().all) raw;
    else
        alias ancestorsOverloads!().all raw;

    alias staticRemoveDuplicates!(sameType, raw) all;
}

unittest
{
    struct Scope1
    {
        interface I     { void foo(); }
        interface J : I { void foo(int); }
        class A : J { abstract void foo(int, int); }
        class B : A { abstract void foo(int, int, int); }
        class C : B
        {
            abstract void foo(int, int, int, int);
            abstract void bar();
        }
        static assert(PackedTuple!(MethodsTuple!(C, "foo"))
            .consistsOf!(I.foo, J.foo, A.foo, B.foo, C.foo));
    }
    // duplicate methods
    struct Scope2
    {
        interface I { void foo(int); }
        interface J { void foo(int); }
        class C : I, J
        {
            void foo(int) {}
        }
        static assert(PackedTuple!(MethodsTuple!(C, "foo"))
            .consistsOf!(C.foo));
    }
}


/***************************************
Returns a tuple of $(D OverloadSet) instances, each of which contains
member function overloads of the same name defined in $(D C) and its
ancestors, filtered with the predicate template $(D pred).

Example:
--------------------
interface I { void foo(); }
interface J { int foo(int); }
class K : I, J
{
    abstract void bar();
    abstract void foo();
    void baz() {} // This will be excluded
}
pragma(msg, MethodOverloadSets!(K, isAbstractFunction).stringof);
--------------------
which prints
--------------------
tuple(OverloadSet!("bar",bar),OverloadSet!("foo",foo,foo))
--------------------
 */
template MethodOverloadSets(C, alias pred = staticYes)
{
    alias MethodOverloadSetsImpl!(C, pred, methodNames!(C)).result
        MethodOverloadSets;
}

private template MethodOverloadSetsImpl(C, alias pred, alias mnames)
{
    static if (mnames.length)
    {
        alias MethodOverloadSetsImpl!(C, pred, mnames[1 .. $]) Next;
        alias staticFilter!(pred, MethodsTuple!(C, mnames[0])) ov;

        static if (ov.length)
            alias TypeTuple!(
                    OverloadSet!(mnames[0], ov),
                    Next.result
                ) result;
        else
            alias Next.result result;
    }
    else
    {
        alias TypeTuple!() result;
    }
}

unittest
{
    struct Scope
    {
        interface I { void foo(); }
        interface J { int foo(int); }
        interface K : I, J
        {
            void bar();
            void foo();
        }
        alias MethodOverloadSets!(K) MOT;
        static assert(MOT[0].name == "bar");
        static assert(is(typeof(MOT[0].overloads)
                    == typeof(TypeTuple!(K.bar))));
        static assert(MOT[1].name == "foo");
        static assert(is(typeof(MOT[1].overloads)
                    == typeof(TypeTuple!(I.foo, J.foo))));
    }
}


